import socket
import base64
import time
import threading
import pickle
import optparse
import Skype4Py
import pprint

cmdConnect, cmdDisconnect, cmdData, cmdError, cmdPing = range(5)
   
def StreamRead(stream):
    try:
        return pickle.loads(base64.decodestring(stream.Read()))
    except EOFError:
        return None
        
def StreamWrite(stream, *obj):
    try:
        stream.Write(base64.encodestring(pickle.dumps(obj)))
    except:
        return 0
    return 1

class DataTunnel(threading.Thread):
    threads = {}
    def __init__(self, sock, stream, newtype, n=None, addr=0):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.sock = sock
        self.stream = stream
        self.type = newtype
        self.client = 0
        self.addr = addr
        self.master = (n==None)
        
        if self.master:
            try:
                n = len(DataTunnel.threads[self.stream])
            except KeyError:
                n = 0
            self.n = n
            print "Create master client connection: ",n
        else:
            print "Create normal server-client socket: ",n
            self.n = n
        
        if n == 0:
            DataTunnel.threads[self.stream] = {self.n:self}
        else:
            DataTunnel.threads[self.stream][self.n] = self
        
    def run(self):
        if self.master:
            StreamWrite(self.stream, cmdConnect, self.n, self.type)
        print 'Opened new connection (%s)' % self.n
        
        if self.type==socket.SOCK_DGRAM:
            while True:
                try:
                    data, self.client = self.sock.recvfrom(4096)
                    if data:
                        if not StreamWrite(self.stream, cmdData, self.n, data):
                            print "Problem with data writing to UDP Client"
                            break
                except socket.error:
                    pass
        else:
            try:
                while True:
                    data = self.sock.recv(4096)
                    if not data:
                        break
                    if not StreamWrite(self.stream, cmdData, self.n, data):
                        print "Problem with data writing to TCP Client"
                        break
            except socket.error:
                pass

            
        self.close()
        
        if self.master:
            StreamWrite(self.stream, cmdDisconnect, self.n)
        print 'Closed connection (%s)' % self.n
        
        del DataTunnel.threads[self.stream][self.n]
        
    def send(self, data):
        if self.type==socket.SOCK_DGRAM:
            try:
                if self.client!=0:
                    self.sock.sendto(data, self.client)
                else:
                    self.sock.sendto(data, self.addr)
            except socket.error:
                pass
        else:
            try:
                self.sock.send(data)
            except socket.error:
                pass
                
    def close(self):
        try:
            self.sock.shutdown(socket.SHUT_RDWR)
            self.sock.close()
        except socket.error:
            pass
            

            
class CLSkypeTunnel:
    def __init__(self):
        self.app_name = "CLSkypeTunnel"
        self.skype = Skype4Py.Skype()
        self.skype.OnApplicationReceiving = self.ApplicationReceiving
        self.skype.Attach()
        self.app = self.skype.Application(self.app_name)
        self.app.Create()
        print "CLSkypeTunnel created."
    
    def start_server(self, addr):
        print "Server started on ", addr
        self.addr = addr
        while True:
            time.sleep(60)
            for stream in self.app.Streams:
                if not StreamWrite(stream, cmdPing):
                    for t in DataTunnel.threads[stream]:
                        DataTunnel.threads[stream][t].close()
                        del DataTunnel.threads[stream][t]
                    del DataTunnel.threads[stream]
                    print "Starange disconnection of user"
                    pprint.pprint(DataTunnel.threads)
    def start_client(self, stype, addr, user):
        if stype==socket.SOCK_DGRAM:
            gsock = socket.socket(type=stype)
            gsock.bind(addr)
            cstream = self.app.Connect(user, True)
            print addr
            DataTunnel(gsock, cstream, socket.SOCK_DGRAM).start()
            print "UDP Client started on", addr, "for user"
            while True:
                pass
        elif stype==socket.SOCK_STREAM:
            gsock = socket.socket(type=stype)
            gsock.bind(addr)
            gsock.listen(5)
            cstream = self.app.Connect(user, True)
            print "TCP Client started on", addr, "for user"
            while True:
                sock, raddr = gsock.accept()
                DataTunnel(sock, cstream, socket.SOCK_STREAM).start()
        
        
    def ApplicationReceiving(self, app, streams):
        for stream in streams:
            obj = StreamRead(stream)
            if obj:
                if obj[0]==cmdData:
                    try:
                        DataTunnel.threads[stream][obj[1]].send(obj[2])
                    except KeyError:
                        print "KeyError here", obj[1], DataTunnel
                elif obj[0] == cmdConnect:
                    print "Want Connect"
                    n = obj[1]
                    sock = socket.socket(type=obj[2])
                    try:
                        if obj[2]==socket.SOCK_STREAM:
                            sock.connect(self.addr)
                            DataTunnel(sock, stream, socket.SOCK_STREAM, n, self.addr).start()
                        else:
                            DataTunnel(sock, stream, socket.SOCK_DGRAM, n, self.addr).start()
                    except socket.error, e:
                        print 'error (%s): %s' % (n,e)
                        StreamWrite(stream, cmdError, n, tuple(e))
                        StreamWrite(stream, cmdDisconnect, n)
                    pprint.pprint(DataTunnel.threads)
                elif obj[0] == cmdDisconnect:
                    try:
                        DataTunnel.threads[stream][obj[1]].close()
                    except KeyError:
                        pass
                elif obj[0] == cmdError:
                    print 'error (%s): %s' % obj[1:2]





